import { pushTarget, popTarget } from "./dep";

/*
    watcher 与 dep 的关系是：
    每个的组件都有一个独立的 watcher ，通过 new Watcher 创建
    每个属性都有一个自己的 dep 属性，用来收集 watcher
    一个属性可以在多个组件中使用，则一个 dep 对应多个 watcher
    一个组件由多个属性组成，则一个 watcher 对应多个 dep
    由上所述 watcher 与 dep 是多对多关系
    所以 dep 需要去收集 watcher，而 watcher 也需要去记录 dep
*/
let id = 0;
/**
 * Watcher 观察者
 * @method get 触发被劫持的属性进行取值更新，并让 dep 收集 watcher
 * @method evaluate 计算属性求值
 * @method addDep 让 watcher 进行记录 dep
 * @method update 让 watcher 触发更新
 * @method depend 让 watcher 上的 dep 去收集上层的 watcher
 */
class Watcher {
    constructor(vm, expOrFn, options, cb) {

        // 获取 vm 实例
        this.vm = vm;
        // Watcher 的标识符
        this.id = id++;

        /*
            渲染 wattcher 的标识符
                render: true 表示该 Watcher 是一个渲染 watcher

            计算属性 watcher 的标识符
                lazy: true 表示该 Watcher 是一个计算属性 watcher，并表示懒执行
                dirty: true  表示脏值标识，计算属性的值被改变，则脏值标识为 true（初始渲染默认为 true），重新进行取值渲染

            watch 函数的 watcher 的标识符
                deep: true 表示该 Watcher 是一个深度监听 watcher
                immediate: true 表示该 Watcher 是一个立即执行 watcher
                user: true 表示该 Watcher 是一个用户自定义的 watcher
        */
        // 获取标识符选项
        this.options = options;

        // 处理用户自定义的 watch 函数 expOrFn 是一个字符串，则需要处理成一个函数
        // 将更新回调函数赋值给 getter，调用 getter 意味着这个函数会发生取值操作
        if (typeof expOrFn === 'string') {
            this.getter = function () {
                return this[expOrFn]
            }
        }
        // 若 expOrFn 是一个函数，则直接赋值
        if (typeof expOrFn === 'function') {
            this.getter = expOrFn;
        }

        // 获取到用户自定义的 watch 函数的回调函数
        this.cb = cb;

        // 存储关联的 dep
        this.deps = [];
        // 存储 depId 进行去重
        this.depsId = new Set();

        // 若是一个懒执行 watcher，则不需要订阅更新，否则渲染 watcher 需要订阅更新
        // 拿到首次取到的值，给 watch 提供旧的值
        this.value = this.options.lazy ? undefined : this.get();

        // 是否首次渲染 watch 函数回调
        if (this.options.immediate) {
            this.cb.call(this.vm, this.value)
        }
    }
    get() {
        // 当创建一个 watcher 时，会把当前的 watcher 存储到栈顶中
        pushTarget(this);
        // 调用 this.getter 方法，让 vm 上的被劫持的属性触发 getter 方法进行取值
        let value = this.getter.call(this.vm);
        // 当前 watcher 已经完成了取值渲染操作后，将当前栈顶 watcher 弹出
        popTarget();
        // 返回取值的结果
        return value
    }
    addDep(dep) {
        // 一个组件对应多个属性，重复使用的属性不在记录 dep，去重避免重复添加
        if (!this.depsId.has(dep.id)) {
            // 将已经记录过的 dep 的 id 存储到 set 中方便去重
            this.depsId.add(dep.id);
            // watcher 记录 dep
            this.deps.push(dep);
            // 调用 dep 的 addSub 方法收集 watcher
            dep.addSub(this);
        }
    }
    evaluate() {
        // 获取计算属性 watcher 中取到的最新的值
        this.value = this.get();
        // 并将脏值标识置为 false，表示该计算属性取到最新的值后进入缓存
        this.options.dirty = false;
    }
    depend() {
        // 让计算属性 watcher 上的 dep 去收集上层的渲染 watcher 去更新
        this.deps.forEach(dep => dep.depend());
    }
    update() {
        // 判断更新时是否是计算属性
        if (this.options.lazy) {
            //  计算属性依赖的值变化了就将脏值标识置为 true
            this.options.dirty = true;
        } else {
            // 将当前的 watcher 暂存起来，批次处理时，可以让 watcher 只执行一次
            queueWatcher(this);
        }
    }
    run() {
        // 数据更新后触发 watch 回调函数
        let oldValue = this.value; // 获取首次取到的旧值
        let newValue = this.get(); // 获取更新后的新值
        // 判断是否是用户自定义的 watch 函数
        if (this.options.user) {
            this.cb.call(this.vm, newValue, oldValue)
        }
    }
}

/*
    vue2 中 nextTick 没有采用某个固定的 API ，而是采用优雅降级的模式
    promise （IE 不兼容）
    MutationObserver （H5 API）
    setImmediate （IE专享）
    setTimeout （兼容性较差）
*/
let timerFunction;
if (Promise) {
    timerFunction = (cb) => {
        Promise.resolve().then(cb);
    }
} else if (MutationObserver) {
    timerFunction = (cb) => {
        // 这里传入的回调是异步执行
        let observer = new MutationObse
        rver(cb);
        // 创建一个文本
        let textNode = document.createTextNode(1);
        // 监控这个文本
        observer.observe(textNode, {
            characterData: true, // 监控文本数据
        })
        // 文本数据改变时，触发回调
        textNode.textContent = 2;
    }
} else if (typeof setImmediate !== 'undefined') {
    timerFunction = setImmediate;
} else {
    timerFunction = setTimeout;
}

/*
   若多个属性同步赋值更新时，dep 通知 watcher 更新会触发多次
   因此为了避免多次触发，将 watcher 暂存起来，等待事件环执行完毕后再执行 watcher
   等待同步代码执行完毕后（所有属性被赋值完毕后）在异步通知 watcher 进行更新操作
*/
// 暂存 watcher 批次处理
let queue = [];
// watcher 去重
let watchersId = new Set();
// 防抖标识符
let pending = false;
// 刷新调度队列，将 watcher 批量更新
function flushSchedulerQueue() {
    // 拷贝一份 queue
    let flushQueue = [...queue];
    // 清空 queue
    queue = [];
    // 清空 watchersId
    watchersId.clear();
    // 防抖标识符还原
    pending = true;
    // 执行栈中的所有 watcher 进行更新操作
    flushQueue.forEach(q => q.run())
}
function queueWatcher(watcher) {
    // 拿到 id 进行去重
    const id = watcher.id;
    if (!watchersId.has(id)) {
        // 将已经记录过的 watcher 的 id 存储到 set 中方便去重
        watchersId.add(id);
        // 将 watcher 暂存起来
        queue.push(watcher);
        // 不管 update 执行多少次，但是这里始终只执行一轮
        if (!pending) {
            timerFunction(flushSchedulerQueue);
            pending = true;
        }
    }
}

/*
    若多开 nextTick 则会导致开了多个异步任务
    这里将 nextTick 维护成一个队列，依次执行
*/
// 暂存 nextTick 中的回调函数批次处理
let callbacks = [];
// 防抖标识符
let waiting = false;
// 回调刷新调度队列 （异步执行）
function flushCallbacks() {
    // 拷贝一份 callbacks
    let cbs = [...callbacks];
    // 清空 callbacks
    callbacks = [];
    // 防抖标识符还原
    waiting = false;
    // 依次执行回调函数
    cbs.forEach(cb => cb());
}
export function nextTick(cb) {
    // 将回调函数暂存到栈中（同步执行）
    callbacks.push(cb);
    // 暂存完后立即执行，防止多次执行
    if (!waiting) {
        timerFunction(flushCallbacks);
        waiting = true;
    }
}

export default Watcher;